/**
 *	Yggdrasil World Framework Toolkit
 *	(A component of the NeuroCoSA toolkit)
 *	Copyright (C) 2001-5 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween
 * @license	LGPL
 */

package org.stumeikle.Yggdrasil;

import java.net.*;
import java.io.*;
import java.util.*;

/** Yggdrasil is the server side control class. The 'main' class of the server.
 *  It receives signals from the client via tcp/ip and initiates new creatures entering/
 *  leaving the virtual environment. 
 *  All code (C) Copyright Stuart Meikle, written 2001-2005.
 */
public class Yggdrasil
{
    public static int		iMaxNumCreatures=100;/** The maximum number of incoming
    						      *  connections we'll accept on first
						      *  principle.
						      */
    public static int		iControlPort = 9999;/** The port number to be used for the
    						     *  Yggdrasil control channel. 
						     */
    public static int		iFirstCreaturePort = 10000; 
    						    /** The first port to be used for the
    						     *  Yggdrasil creature input/output
						     *  lines.  
						     */    
    String			iConnectType;	    /** ID signalling which kind of creature
    						     *  is trying to connect
						     */
    ServerSocket		iControlSocket = null;
    Socket 			iClientSocket = null;
    int				iNumCreatures;
    FountainOfLife		iExportToWorld;     /** This class acts as a mutex or semaphore
    						     *  . Incoming connections are signalled to the
						     *  World structure via this class. 
						     */
    World			iWorld;		   /** The whole virtual world */
    Vector<Life>		iCreatureWrappers; /** A container for creatures present in the
    						    *  world. 
						    */ 
			
    /**
     * Constructor. This method never returns! Main Yggdrasil event loop is contained within.
     * 
     */
    public Yggdrasil()
    {
	//set some defaults up for the vars
	iControlPort=9999;
	iMaxNumCreatures = 10;
	iWorld = null;
	//iBodyFactory = null;
    }

    /** methods called by derived forms to initialise the parameters 
     */
    public void	setControlPort( int i )
    {
	iControlPort = i;
    }

    public void	setMaxNumCreatures(int i)
    {   iMaxNumCreatures = i; }

    public void	setWorld( World w )
    { iWorld = w; }

    //public void	setBodyFactory( BodyFactory f )
    //{ iBodyFactory = f; }

    /** start method. set things up
     */
    public void	start() throws Exception
    {
	//initialise the internals. should be done already.
	//iControlPort = controlport;
	iFirstCreaturePort = iControlPort + 1;
	iCreatureWrappers  = new Vector<Life>(iMaxNumCreatures);
	iExportToWorld     = new FountainOfLife();
	
	if (iWorld != null)	
	    iWorld.setFountainOfLife( iExportToWorld );
	else
	{
	    iWorld        = new World();
	    iWorld.setFountainOfLife( iExportToWorld );
	}

	//next line moved into the world
	//iWorld.setBodyFactory(iBodyFactory);
	iWorld.init();
	iConnectType = null;

	//sit on the controlport and wait for creatures to connect
        System.out.println("Yggdrasil:");
	System.out.println("Yggdrasil:**** tree of life starting ... ****");

	try
	{
	    BindToSocket( iControlPort );
	}
	catch(IOException e)
	{
	    System.out.println("Yggdrasil:");
	    System.out.println("Yggdrasil: Failed to bind to port ("+iControlPort+")");
	    System.out.println("Yggdrasil:");
	    throw e;
	}

        System.out.println("Yggdrasil:");
        System.out.println("Yggdrasil:Ok. Bound successfully to port ("+iControlPort+")");
        System.out.println("Yggdrasil:Waiting on clients");
	iNumCreatures=0;

	// spawn World
	iWorld.start();

	WaitForCreatures();
    }

    /** Called by the Yggdrasil contrustor. The main Yggdrasil event loop.
     *  This method waits for incoming connection requests, create Life s for them when
     *  then connect and passes them on to the world.
     */
    void	WaitForCreatures() throws Exception
    {
	do
	{
    	    try
	    {
		//wait for connection... then determine connectee's type...
		iClientSocket = iControlSocket.accept();
		getCreatureType( iClientSocket );
	    }
	    catch (IOException e)
	    {
        	System.err.println("Yggdrasil:Accept failed.");
		try{iControlSocket.close();}catch(Exception et){}
        	throw e;
    	    }

    	    System.out.println("Yggdrasil:Accepted an incoming creature client!");
	    System.out.println("Yggdrasil:	Assigning it a port number");

	    int	connectPort = iFirstCreaturePort + (2*iNumCreatures);
	    try
	    {
		SendPortID( connectPort );
		iNumCreatures++;
	    } catch ( IOException e )
	    {
		System.out.println("Yggdrasil:	But failed!");
		throw e;
	    }

	    //FIX -> check the number of incoming connections!
	    
	    //ok, create a new thread to deal with the new client
	    //and return to waiting for connections
	    //20031103 senses and actions now depend on the type
	    //of the connecting creature
	    //20031106 stumeikle. no real need to create the senses/actions here
	    Life	life  = createLife(iConnectType, connectPort);

	    iCreatureWrappers.add(life);
	    life.start(); //alakazam!

	    System.out.println("Yggdrasil: sending signal to world");
	    iExportToWorld.setNewCreatureType(iConnectType);	//BLOCKS!!
	    iExportToWorld.setLife( life );
	    iExportToWorld.setNewCreaturePresent();

	    System.out.println("Yggdrasil: got acknowledge from world");
	}while(true);
    }

    void BindToSocket( int cp ) throws IOException
    {
	iControlSocket = new ServerSocket(cp);
    }


    /** Causes Yggdrasil to send a control signal to the connecting client to ask
     *  it to use port p for sending actions
     */
    void	SendPortID( int p ) throws IOException
    {
	//tell the client that it should talk on port p
	PrintWriter out = new PrintWriter(iClientSocket.getOutputStream(), true);
	out.println( p );
	out.close();
    }

    /** Reads the incoming creature type from the control stream
     */
    void	getCreatureType( Socket s )
    {
    	BufferedReader	in;
	String		inputLine;

	try
	{
	    in = new BufferedReader(new InputStreamReader(s.getInputStream()));
	    inputLine = in.readLine();

	    if (inputLine !=null)
	    {
	    	iConnectType = new String( inputLine );
	    }
	}catch(Exception e)
	{}
    }

    Life 	createLife(String t, int port)
    {
       return new Life(t,port);

//	return null;//but should really be an exception thrown...
    }


    /** Main. Start the server here
     */
    public static void main( String[] args )
    {
	new Yggdrasil();
    }
}

